home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 5 / Apprentice-Release5.iso / Environments / PowerMacOberon feb96 / Source / Report.Pers (.txt) < prev    next >
Encoding:
Oberon Text  |  1995-08-16  |  93.7 KB  |  746 lines  |  [TEXT/.Ob4]

  1. Syntax10.Scn.Fnt
  2. ParcElems
  3. Alloc
  4. Syntax16m.Scn.Fnt
  5. Syntax12.Scn.Fnt
  6. FoldElems
  7. Syntax14b.Scn.Fnt
  8. StyleElems
  9. Alloc
  10. FirstIndented1
  11. Helvetica14.Scn.Fnt
  12. Helvetica12.Scn.Fnt
  13. FirstIndented1
  14. Syntax12b.Scn.Fnt
  15. Syntax12i.Scn.Fnt
  16. source
  17. source
  18. LinkElems
  19. Alloc
  20. Report.Pers
  21. FirstIndented1
  22. source
  23. Report.Pers
  24. source
  25.     Syntax10i.Scn.Fnt
  26. Syntax10b.Scn.Fnt
  27. MarkElems
  28. Alloc
  29. Report.Pers
  30. FirstIndented1
  31. source
  32. FirstIndented1
  33. Report.Pers
  34. source
  35. Chicago12.Scn.Fnt
  36. FirstIndented1
  37. Syntax10.Scn.Fnt
  38.  Fig. module hierarchy 
  39. Figure
  40. PictElems
  41. Alloc
  42. OberonD
  43. Commando Interface
  44.  Persistent
  45. Programming Interface
  46. 1PersKernel
  47. Persistent memory and root
  48. management.
  49. PersSys
  50. system dependent proc.
  51. $PersTypes
  52. Persistent Type management
  53. Palatino
  54. OberonD
  55. Commando Interface4
  56. Persistent
  57. Programming Interface4
  58. PersKernel
  59. Persistent memory and root
  60. management.4
  61. PersSys
  62. system dependent proc.(
  63. PersTypes
  64. Persistent Type management
  65. Chicago10.Scn.Fnt
  66. Report.Pers
  67. Report.Pers
  68. Report.Pers
  69. Report.Pers
  70. Report.Pers
  71. Report.Pers
  72. Report.Pers
  73. Report.Pers
  74. FirstIndented1
  75. Helvetica10.Scn.Fnt
  76. Report.Pers
  77. FirstIndented1
  78. Report.Pers
  79. Figure
  80. dddddd
  81. ff33@
  82. ffffff
  83. ffff33
  84. ff33ff
  85. ff3333
  86. 33ffff
  87. 33ff33
  88. 3333ff
  89. 333333
  90. wwwwww
  91. UUUUUU
  92. DDDDDD
  93. """"""
  94. Figure
  95. dddddd
  96. ff33@
  97. ffffff
  98. ffff33
  99. ff33ff
  100. ff3333
  101. 33ffff
  102. 33ff33
  103. 3333ff
  104. 333333
  105. wwwwww
  106. UUUUUU
  107. DDDDDD
  108. """"""
  109. FirstIndented1
  110. FirstIndented1
  111. Report.Pers
  112. Report.Pers
  113. Report.Pers
  114. FirstIndented1
  115. Report.Pers
  116. Report.Pers
  117. FirstIndented1
  118. Report.Pers
  119. Report.Pers
  120. Report.Pers
  121. FirstIndented1
  122. FirstIndented2
  123. FirstIndented1
  124. source
  125. FirstIndented1
  126. source
  127. FirstIndented1
  128. source
  129. FirstIndented1
  130. FirstIndented1
  131. Report.Pers
  132. Report.Pers
  133. FirstIndented1
  134. Report.Pers
  135. Report.Pers
  136. FirstIndented1
  137. FirstIndented1
  138. Report.Pers
  139. FirstIndented1
  140. source
  141. FirstIndented1
  142. Including Persistence in the Oberon-System 
  143. Markus Knasm
  144. Institute for Computer Science (Systemsoftware)
  145. Johannes Kepler University, Altenbergerstrasse 69, A-4040 Linz
  146. knasmueller@ssw.uni-linz.ac.at
  147. Introduction    
  148.     What is Persistence
  149. Persistence in the Oberon System
  150.     Working with
  151.     An Example
  152.     Commands of OberonD
  153.     User interface
  154.     Restrictions
  155.         anonym types
  156.         procedure variables
  157. Implementation
  158.     Overview
  159.         General Introduction: When? Where? How?
  160.     Finalization 
  161.         in Mark & Sweep
  162.         Generation of automatic Mapper
  163.     Trap
  164.         Optimiziation
  165.     Persistent Memory Management
  166.         Persistent Roots
  167.         PersTypes
  168.         Make a transient object persistent
  169.         Persistent Garbage Collection   Stop & Copy
  170.     Programming Interfaces
  171.     necessary changes in the Oberon System
  172.     platform dependencies => Windows
  173. Conclusion and Future Work
  174.     Schema evolution a.s.o.
  175.     different heaps
  176. Appendix
  177.     Definition ISFiles.Mod
  178.     Heap format in EBNF - grammar
  179.     Error-Codes
  180.     How to get Oberon-D
  181.     References
  182. Abstract
  183. Oberon [ReWi92] and Oberon-2 [M
  184. Wi91] are general purpose programming languages in the tradition of Pascal and Modula-2. But Oberon [WiGu89] is also a modular, single-threaded operating system aiming expecially at single-user operation of workstations. It is used in daily work as well as in programming courses. As our experience with Oberon shows, one missing point is the existence of database functionalities, like persistence or recovery.
  185.     This report describes the project Oberon-D, which aim is the including of database functionalities in the Oberon system. The first step is the including of persistence, the object's property to outlive the programs, that create it. The using of persistence is shown as well as the implementation.
  186. Contents
  187. 1 Introduction
  188. The including of database functionalities in the Oberon system is an ongoing project, named Oberon-D. In a first step Oberon-D should manage the introduction of persistence. Before beginning our work we defined different aims for this project:
  189.     As simple as possible.
  190.     No changes in the Oberon langugage.
  191.     No (less) changes in the Oberon system.
  192.     No (less) platform dependencies. There should be only one short module which has to be changed when porting Oberon-D from one platform to another (e.g. from PowerMac-Oberon to Windows-Oberon).
  193.     Minimal use of memory.
  194.     No interference with normal work. No additional delays.
  195.     Oberon_D should work like other Oberon applications. That means especially that working with persistent data should not differ from working with transient data. For example there should also be a garbage collector [.???.] for persistent data.
  196. Persistence is an attribute describing an object`s lifetime. In a language with persistence, data objects survive between program runs. Persistence is the most important property of an object oriented database programming system. The opposite is transient, which means that transient data last only for the invocation of a program.
  197. There are certain principles that should govern system design [AtBu87, p. 109]:
  198.     Persistence should be orthogonal, i.e. a property of arbitrary values and not limited to certain types.
  199.     All values should have the same rights to persistence.
  200.     While a value persists, so should its description (type).
  201.     Furthermore, the programmer should be able to manipulate the persistent objects with normal expression syntax, i.e. physical I/O should be transparent to the programmer.
  202. All these mentioned points, concerning Oberon and persistence were considered by the development of Oberon-D.
  203. 2 Persistence in the Oberon System
  204. It was clear that manipulating of persistent objects should work like manipulating of transient objects. Therefore this point must not be explained here. But there are still some interesting points: How to make an object persistent and how to address this persistent data later.
  205. 2.1 Working with persistence
  206. By following our aim not to change the Oberon language, it was not possible to introduce a new statement to allocate persistent data, like e.g. the operator pnew in ODE [AgGe89]. Therefore we decided to take another persistent mechanism, that of reachability by a persistent root. Each allocated object can become a persistent root, by using the function Persistent.SetRoot (obj, n), where obj is an existing oberon object and n is a user-defined unique key of type String (= ARRAY OF CHAR). All objects referenced by a persistent root will be persistent objects, if there is not anything else defined (see later).
  207.     All Oberon applications can now address a root with the name n (at any time) by using the function Persistent.GetRoot (n, obj).
  208. The following source code shows how to make a string (identified by the root myroot) persistent. 
  209.     PROCEDURE MakeStringPersistent;
  210.         VAR s: POINTER TO ARRAY OF CHAR;
  211.     BEGIN
  212.         NEW (s, 32); s := "Oberon-D";
  213.         Persistent.SetRoot (s, "myroot")
  214.     END MakeStringPersistent;
  215. To address this string after and print it to the output viewer, the following source code can be used:
  216.     PROCEDURE AddressAndPrintString;
  217.         VAR s: POINTER TO ARRAY OF CHAR;
  218.     BEGIN
  219.         Persistent.GetRoot ("myroot", s);
  220.         Out.String (s)
  221.     END AddressAndPrintString;
  222. Making an object persistent means that the object survives the termination of the program. The only solution to make this possible is to map the object to an external representation (and back). Normally the object's properties can be written to a file in an automatic way. Oberon-D knows the structure of each object (see section 
  223. ) and can therefore decide how to map the object. Unfortunately there are some problems in the case of automatic persistency (for details see [Tem94, p. 115ff]):
  224.     Closure Control (e.g Fonts)
  225.      Implicit Dependencies (e.g. File objects)
  226.     Partially used Arrays (e.g. character arrays which have a zero terminated string as contents)
  227. Because of this reasons Oberon-D gives a user the possibility to implement a mapper for each type. This mapper must be from the structure
  228.     Mapper = PROCEDURE (o: SYSTEM.PTR);
  229. and has to use the read and write procedures offered by the module Persistent (see section 
  230. ). To order Oberon to use the self-implemented mappers one has to register them with the procedure Persistent.RegisterType (t, read, write). After such an registering operation the mappers read and write are taken instead of the automatic mappers. Be careful, it the specified type t is at record extension level n, the registered mappers are responsible for handling only the fields introduced at level n.
  231. Another interesting point is the deleting of persistent data. In Oberon transient data is deleted by a garbage collector, which means that there is no way to explicitly dispose an allocated block. The garbage collector finds the blocks that are not used any more and makes them available for allocation again. A garbage collector frees a programmer from the non-trivial task of deallocating data structures correctly and thus helps to avoid errors.
  232.     In Oberon-D there also exists a garbage collector for persistent data. All objects which are not accessible by a persistent root are garbage and will be removed by the next run of the garbage collector, started by calling the command OberonD.Collect. You can remove a persistent root with name n by the function Persistent.RemoveRoot (n). After that the object referenced by the root n is still existing, but is only transient and not even more persistent.
  233. 2.2 An Example
  234. The following source code shows a short example of using Oberon-D. This module offers a simple list of elements with the properties name and number and the commands Init (to allocate the list), Insert (to insert a list of elements in the list) and Print (to prints the list). Each list has a header info containing a property font, which determines the printing font. The objects of type Elem are mapped by automatic mappers, the objects of type List are mapped by the registered mappers WriteList and ReadList. These mappers must be registered by the command RegisterMappers.
  235. MODULE Example;
  236.     IMPORT S := SYSTEM, Fonts, In, Modules, Oberon, OberonD, Out, Persistent, Texts, Types;
  237.     TYPE
  238.         List = POINTER TO ListDesc;
  239.         Elem = POINTER TO ElemDesc;
  240.         ListDesc = RECORD
  241.             first: Elem;
  242.             font: Fonts.Font;
  243.         END;
  244.         ElemDesc = RECORD
  245.             number: LONGINT;
  246.             name: ARRAY 32 OF CHAR;
  247.             next: Elem;
  248.         END;
  249.     PROCEDURE WriteList (o: S.PTR);  (* writes the list o to the persistent heap *)
  250.         VAR l: List; str: ARRAY 32 OF CHAR;
  251.     BEGIN
  252.         l := S.VAL (List, o);
  253.         Persistent.WriteObj (l.first); 
  254.         IF l.font # NIL THEN COPY (l.font.name, str) ELSE str := "" END;
  255.         Persistent.WriteString (str)
  256.     END WriteList;
  257.     PROCEDURE ReadList (o: S.PTR); (* reads the list o from the persistent heap *)
  258.         VAR l: List; str: ARRAY 32 OF CHAR;
  259.     BEGIN
  260.         l := S.VAL (List, o);
  261.         Persistent.ReadObj (l.first); Persistent.ReadString (str);        
  262.         IF str # "" THEN l.font := Fonts.This (str) ELSE l.font := Fonts.Default END
  263.     END ReadList;
  264.     PROCEDURE RegisterMappers*;
  265.     BEGIN 
  266.         Persistent.RegisterType (Types.This (Modules.ThisMod ("Example"), "ListDesc"), 
  267.             ReadList, WriteList)
  268.     END RegisterMappers;
  269.     PROCEDURE Init*;   (* root font *)
  270.         VAR l: List; root, font: ARRAY 32 OF CHAR; 
  271.     BEGIN
  272.         In.Open; In.Name (root);  (* persistent list can be identified by a key: root*)
  273.         In.Name (font); (* printing font *)
  274.         IF In.Done THEN 
  275.             NEW (l); l.first := NIL; l.font := Fonts.This (font);
  276.             Persistent.SetRoot (l, root);
  277.             IF Persistent.res = Persistent.alreadyExists THEN Out.String ("This root already exists") END
  278.         END
  279.     END Init;
  280.     PROCEDURE Insert*;   (* root {name nr} *)
  281.         VAR l: List; e: Elem; nr: LONGINT; name, root: ARRAY 32 OF CHAR; 
  282.     BEGIN
  283.         In.Open; In.Name (root);  (* persistent list can be identified by a key: root *)    
  284.         IF In.Done THEN Persistent.GetRoot (root, l) END;
  285.         IF ~In.Done OR (l = NIL) THEN RETURN END; (* no such existing root *) 
  286.         In.Name (name); In.LongInt (nr); 
  287.         WHILE In.Done DO
  288.             NEW (e); COPY (name, e.name); e.number := nr; e.next := l.first; l.first := e;
  289.             In.Name (name); In.LongInt (nr)
  290.         END
  291.     END Insert;
  292.     PROCEDURE Print*;  (* root *)
  293.         VAR l: List; e: Elem; root: ARRAY 32 OF CHAR; w: Texts.Writer;
  294.     BEGIN
  295.         In.Open; In.Name (root); (* persistent list can be identified by a key: root *)
  296.         IF In.Done THEN Persistent.GetRoot (root, l) END;
  297.         IF ~In.Done OR (l = NIL) THEN RETURN END; (* no such existing root *) 
  298.         e := l.first; Texts.OpenWriter (w); Texts.SetFont (w, l.font);
  299.         WHILE e # NIL DO
  300.             Texts.WriteString (w, e.name); Texts.WriteInt (w, e.number, 10); Texts.WriteLn (w);
  301.             e := e.next
  302.         END;
  303.         Texts.Append (Oberon.Log, w.buf)
  304.     END Print;
  305. BEGIN OberonD.Init
  306. END Example.
  307. 2.3 Commands of Oberon-D
  308. Module OberonD offers commands to work with roots and to check the state of the database. All tools offered by this module can also be used through a programm by calling the corresponding procedures of the module Persistent (see section 
  309.     Init activates the database. This command or the corresponding procedure Persistent.Install must be called by starting the work with Oberon-D. 
  310.     AllocateRoot (name t | ^) allocates a new root of type t with key name.
  311.     RemoveRoot (name | ^) removes the persistent root with key name. After that the object referenced by the root name is still existing, but is only transient and not even more persistent.
  312.     Collect starts the garbage collector of the persistent heap. This function is only available, if the database is off-line. That means garbage collection could not be started as long there are existing transient references to persistent data.
  313.     Watch shows the actual size of the persistent heap and the actual number of persistent objects.
  314.     ShowRoots shows the list of existing persistent roots.
  315.     About writes the version number of Oberon-D in the log viewer.
  316. 2.4 User Interface
  317. The relevant procedures for a user of Oberon-D are offered by the module Persistent. This module is the programming interface to the world of Oberon-D.
  318. DEFINITION Persistent;
  319.     IMPORT SYSTEM, Modules, PersTypes, Types;
  320.     CONST
  321.         ok = 0; alreadyExists = 1; undefinedRoot = 2; 
  322.         databaseOnline = 3; mapperNotRegistered = 4;  (* error codes *)
  323.         res: INTEGER;
  324.     PROCEDURE GetRoot (root: ARRAY OF CHAR; VAR o: SYSTEM.PTR);
  325.     PROCEDURE Install;
  326.     PROCEDURE Read (VAR x: SYSTEM.BYTE);
  327.     PROCEDURE ReadBool (VAR b: BOOLEAN);
  328.     PROCEDURE ReadInt (VAR x: INTEGER);
  329.     PROCEDURE ReadLInt (VAR x: LONGINT);
  330.     PROCEDURE ReadLReal (VAR x: LONGREAL);
  331.     PROCEDURE ReadObj (VAR o: SYSTEM.PTR);
  332.     PROCEDURE ReadProc (VAR proc: SYSTEM.PTR);
  333.     PROCEDURE ReadReal (VAR x: REAL);
  334.     PROCEDURE ReadSet (VAR s: SET);
  335.     PROCEDURE ReadString (VAR x: ARRAY OF CHAR);
  336.     PROCEDURE RegisterType (t: Types.Type; read, write: PersTypes.Mapper);
  337.     PROCEDURE RemoveRoot (root: ARRAY OF CHAR);
  338.     PROCEDURE SetRoot (o: SYSTEM.PTR; root: ARRAY OF CHAR);
  339.     PROCEDURE Write (x: SYSTEM.BYTE);
  340.     PROCEDURE WriteBool (b: BOOLEAN);
  341.     PROCEDURE WriteInt (x: INTEGER);
  342.     PROCEDURE WriteLInt (x: LONGINT);
  343.     PROCEDURE WriteLReal (x: LONGREAL);
  344.     PROCEDURE WriteObj (o: SYSTEM.PTR);
  345.     PROCEDURE WriteProc (proc: SYSTEM.PTR);
  346.     PROCEDURE WriteReal (x: REAL);
  347.     PROCEDURE WriteSet (s: SET);
  348.     PROCEDURE WriteString (x: ARRAY OF CHAR);
  349. END Persistent.
  350. State
  351.     res indicates the success of an operation. If res is ok after an operation, the operation was successful and its result is valid. Otherwise an error occured. The possible error codes can be seen in appendix 
  352. Initialization routine
  353.     Install activates the database. This procedure must be called by starting the work with Oberon-D.
  354. Reading
  355.     Read (x) reads the next byte x from the persistent heap.
  356.     ReadInt (i) reads an integer number i from the persistent heap.
  357.     ReadLInt (i) reads a long integer number i from the persistent heap.
  358.     ReadReal (x) reads a real number x from the persistent heap.
  359.     ReadLReal (x) reads a long real number x from the persistent heap.
  360.     ReadString (s) reads a sequence of character (including the terminating 0X) from the persistent heap and returns it in s. The actual paramter corresponding to s must be long enough to hold the character sequence plus the terminating 0X.
  361.     ReadSet (s) reads a set s from the persistent heap.
  362.     ReadBool (b) reads a boolean value b from the persistent heap.
  363.     ReadObj (o) reads a persistent reference to the object o from the persistent heap.
  364.     ReadProc (proc) reads the procedure proc from the persistent heap. When using ReadProc the procedure variable must be converted to the type SYSTEM.PTR with the function SYSTEM.VAL, because it is not possible to assign procedure variables to SYSTEM.PTR (see also [Tem94, p. 136]).  
  365. Writing
  366.     Write (x) writes the byte x to the persistent heap.
  367.     WriteInt (i) writes the integer number i to the persistent heap.
  368.     WriteLInt (i) writes the long integer number i to the persistent heap.
  369.     WriteReal (x) writes the real number x to the persistent heap.
  370.     WriteLReal (x) writes the long real number x to the persistent heap.
  371.     WriteString (s) writes the sequence of character s (including the terminating 0X) to the persistent heap.
  372.     WriteSet (s) writes the set s to the persistent heap.
  373.     WriteBool (b) writes the boolean value b to the persistent heap.
  374.     WriteObj (o) writes the persistent reference to the object o to the persistent heap. If o is not a persistent object, it will be marked as one.
  375.     WriteProc (proc) writes the procedure proc to the persistent heap. For the type of parameter proc see ReadProc.
  376. Root management
  377.     GetRoot (r, o) retrieves the persistent root o identified by the key r.
  378.     SetRoot (o, r) marks the object o as a persistent root accesable through the key r.
  379.     RemoveRoot (r) removes the root identified by the key r from the root list. After that the object referenced by the root r is still existing, but is only transient and not even more persistent.
  380. Miscellaneous
  381.     RegisterType (t, read, write) registers the read and write mappers for a transient type t.
  382. Example for a persistent mapper:
  383. MODULE Mapper;
  384.     IMPORT S := SYSTEM, Modules, Persistent, Types;
  385.     TYPE 
  386.         MyType = POINTER TO MyTypeDesc;
  387.         MyTypeDesc = RECORD
  388.             i: INTEGER;
  389.             l: LONGINT;
  390.             r: REAL;
  391.             s: SET;
  392.             str: ARRAY 32 OF CHAR;
  393.             obj: MyType;
  394.             proc: PROCEDURE (VAR ch: CHAR);
  395.         END; 
  396.     PROCEDURE ReadMyType (o: S.PTR);
  397.         VAR m: MyType;
  398.     BEGIN
  399.         m := S.VAL (MyType, o);
  400.         Persistent.ReadInt (m.i);
  401.         Persistent.ReadLInt (m.l);
  402.         Persistent.ReadReal (m.r);
  403.         Persistent.ReadSet (m.s);
  404.         Persistent.ReadString (m.str);
  405.         Persistent.ReadObj (m.obj);
  406.         Persistent.ReadProc (S.VAL (S.PTR, m.proc))
  407.     END ReadMyType;
  408.     PROCEDURE WriteMyType (o: S.PTR);
  409.         VAR m: MyType;
  410.     BEGIN
  411.         m := S.VAL (MyType, o);
  412.         Persistent.WriteInt (m.i);
  413.         Persistent.WriteLInt (m.l);
  414.         Persistent.WriteReal (m.r);
  415.         Persistent.WriteSet (m.s);
  416.         Persistent.WriteString (m.str);
  417.         Persistent.WriteObj (m.obj);
  418.         Persistent.WriteProc (S.VAL (S.PTR,m.proc))
  419.     END WriteMyType;
  420.     PROCEDURE InstallMappers*;
  421.     BEGIN 
  422.         Persistent.RegisterType (Types.This (Modules.ThisMod ("Mapper"), "MyTypeDesc"), 
  423.             ReadMyType, WriteMyType)
  424.     END InstallMappers;
  425. END Mapper.
  426. 2.5 Restrictions
  427. The mappers are based on the run time data structures [M
  428. Wi ...???] offered by the Oberon system. Oberon stores some information about a type in so-called type descriptors. This type descriptors contains for example information about the location of pointers or the type name. These two informations are in most cases enough to generate automatic mappers or to allow a programmer to install registered mappers for one type, but there are two cases in which the type desribtor contains not enough information.
  429.     There is no possibility to identify an anonym type [????] (e.g. VAR a: POINTER TO RECORD END) by its name. Therefore it is not possible to register mappers for an anonym type. The automatic mapper is always used. This restriction should not be a big problem, because objects of an anonym type are most times used only as local variables and surely not as persistent data.
  430.     The type descriptor contains no information about the record components, with the exception of pointers. Therefore an automatic mapper writes and reads the memory block as it is, with no different treatment of e.g. integer, long integer or procedure variables. This means that it is not possible to take the persistent heap from a computer with little endian format to a computer with big endian format or vice versa. As well it is not possible to use automatic mappers for a record containing a procedure variable. A solution for these problems would be to extend the reference information, generated by the Oberon compiler (see [Tem94, p.93f]).
  431. 3 Implementation
  432. This section describes some interesting parts of the implementation of Oberon-D. For details see the source code. Modules and operations of the Oberon system can be studied in [Rei91].
  433. 3.1 Overview
  434. Figure ??? shows the module hierarchy of Oberon-D.
  435. Figure ???.Module hierarchy (arrows denote import)
  436. The programming interfaces of the modules OberonD and Persistent were shown in the sections 
  437.  and 
  438. . The other modules are more for internal use. Their programming interfaces will be shown in 
  439.     A first decision had been made to define how to identify a persistent object, what means how to distinguish two different persistent objects. This is managed by the object identifier (OID), a unique key for each persistent object.
  440.     To make an object persistent it is necessary to map the object to an external representation. This process is declared in this and the following section. There are three main points to decide in this process: When should the object mapped, where should it survive, and how works this process?
  441. When?
  442. This point was easy to decide, because of the implementation of the Oberon memory management, there is only one possible time to map these objects. The Oberon system uses the Mark & Sweep [.???] garbace collector algorithm, what means that at the moment after the Mark phase and before the Sweep phase is the right time to map an unmarked persistent object. Each moment before would mean that the object could be changed after mapping, because there could be existing references to it. After Sweep, mapping the object would be impossible, because the object does not longer exist. 
  443. Where?
  444. All persistent objects are stored in one persistent heap, defined by the variable PersKernel.heap at the position x, given through the OID of the object. In future versions of Oberon-D it should be thought over the existence of different heaps (see also section 
  445. Mapping an object between Mark and Sweep is accompanied with different problems (one is not allowed to allocate memory or to use methods), therefore a simple technique was used for this finalization [Tem94, p.83ff]. The Oberon module Kernel (responsible for the Oberon memory management) offers a procedure Kernel.RegisterObject (obj, fin) where fin is a procedure variable of type Finalizer = PROCEDURE (obj: SYSTEM.PTR) and obj is an object which should be finalized before being reclaimed by the garbage collector. The module Kernel checks between Mark and Sweep, whether there is an registered object which sould be reclaimed and marks it in a special way. After the sweep phase, Kernel calls the registered finalization procedures for all special marked objects. The implementation of this finalization strategy is shown in [Tem94, p.87f] and was a little change in the Oberon system (see also section 
  446.     The job of Oberon-D is now to register the finalization procedure Persistent.Fin for all persistent objects, which maps an object to an external presentation. This procedure is explained in section 
  447. Another interesting point is the loading of a persistent object. This has to be done at the time of an access to a property of the persistent object. Before such an access the loading is not necessary and any transient reference to such an object contains the negative OID of the object. Before such accesses the Oberon compiler generates NIL-Traps [????], which are causes if the object is equal NIL. A little change to the compiler (see section 
  448. ) causes the trap also if the object contains a value lower than zero. Installing an own trap handler (Persistent.Trap) allows Oberon to load the object at this time. The implementation of the trap handler is explained in section 
  449. 3.2 Finalization
  450. 3.2.1 Overview
  451. The finalization is managed through the procedure Persistent.Fin, called by the Oberon module Kernel. This procedure has to do following things:
  452.     Determining the object's OID. For this purpose a relation of memory addresses to OIDs must be available. This relation is stored in a BTree (see Appendix 
  453.     After that a Files.Rider [Rei91] has to be set on PersKernel.heap at the position x, defined by the objects OID.
  454.     Writing a start sign to the heap. The start signs determines wheter the object is an array (PersKernel.array) or a record (PersKernel.record).
  455.     Writing the type of the object to the heap. In most cases only the type number is written to the external memory (for details see below). This type information is necessary for loading the object back.
  456.     Writing the object information to the heap. This means to call the (automatic or registered) mappers for each type extension level. If the object is an array, also the array information (number of dimensions, number of elems for each dimension) has to be written to the heap. Three different cases are possible by the storage of arrays, they are mentioned below.
  457. Writing the object information to the external memory is managed by procedures of Persistent (Write, WriteInt ...). In most cases these procedures are calling the corresponding Files procedures (e.g. Files.Write, Files.WriteInt ...), but there are two different special cases:
  458.     WriteObj (o1) writes only the OID of the object o1 to the persistent heap. If o1 is not a persistent object it will be marked as a persistent object (see section 
  459.     WriteProc (proc) writes the name of the procedure proc and the module defining the procedure to the persistent heap. 
  460. 3.2.2 Automatic Generation of Object Mappers
  461. The automatic storage can be done by the procedure PersSys.Automatic. As explained above the module PersSys is a system-dependent module, what means that the automatic generation of mappers depends on the underlying system. This, because the type information is necessary, and this information is not exactly equal on all Oberon implementations.
  462.     The idea of an automatic mapper is simple. Figure ??? shows the type information offered by the Oberon system (in the shown case: PowerMac-Oberon).
  463. Figure ???. Type descriptor
  464. PersSys.Automatic for a type t has now to take the memory area of the fields introduced by type t and writes it to the heap byte by byte, with the exception of pointers. These fields can be extracted with the using of the type information (ptroff x) and are stored with Persistent.WriteObj.
  465. 3.2.3 Storage of objects of an anonym type
  466. Objects of an anonym type are stored with the help of automatic generated mappers. Because of the impossibility to identify an anonym type by number or name, the type information has to be stored in the heap. So the size of the object on persistent and transient heap, an eventually existing basetype, the module defining the type, and the pointer offsets have to be stored, too.
  467. 3.2.4 Storage of Arrays
  468. The programming language Oberon offers also the possibility to allocate open arrays on the heap. An open array is a pointer to an array of type (e.g. POINTER TO ARRAY OF CHAR). Depending on the type three different kinds of open arrays can be distinguished (Array of Record, Array of Pointer, Array of simple type). The necessary information about these arrays is available through the array descriptors offered by the Oberon system (see fig. ???). The construction of these descriptors depends on the undlerlying system (in the shown case PowerMac-Oberon is used).
  469. Figure ???. Array descriptors
  470. For the reconstructing of the stored arrays in the loading phase Oberon-D needs the following information:
  471.     number of dimensions
  472.     length of each dimension
  473.     type of the array elements
  474. After storing this information, each element of the array has to be mapped, as described above, like an ordinary persistent object.
  475. 3.3 Loading of persistent objects
  476. As mentioned above the trap handler Persistent.Trap is responsible for the loading of persistent objects. Because of a little change in the Oberon compiler a NIL-trap [????] causes by an access to a not-loaded persistent object. The job of Persistent.Trap is now to determine the register which contents causes the NIL-trap. If the contents of the register is equal zero than the standard trap handler must be called, otherwise the absolute value of the register contents is equivalent to the persistent object's OID. In this case the trap handler has to load the persistent object in the transient heap. To reach this aim the operation Persistent.Load is responsible. This procedure has to do the following things:
  477.     Determining that the object is not already loaded: For this purpose a relation of OIDs to memory addresses must be available. This relation is stored in a BTree (see Appendix 
  478. ). If the object is already loaded the contents of the register is set to the memory address of the object.
  479.     If the object is not already loaded, a Files.Rider [Rei91] has to be set on PersKernel.heap at the position x, defined by the object's OID.
  480.     Reading of the start sign and the type information from the heap (equivalent to writing, see section 
  481.     Allocating of an object of the read type.
  482.     Reading the object information from the heap (equivalent to writing, see section 
  483.     The loaded object has to been marked as a persistent object. This means it must registered in the two relations (memory addresses to OIDs and OIDs to memory addresses) and in the Kernel finalization queue.
  484.     Setting the contents of the register to the memory address of the object. 
  485. As mentioned above, only the register contents is set to the memory address. This means, that a next access to this persistent object, causes another trap. To prevent this, three optimiziation strategies are implemented:
  486.     Backwards scanning of the machine instructions, to get the load instruction where the patched register is loaded from memory. Thereby is is possible to get the memory address of the pointer and set the memory pointer to the correct value, preventing further traps. Unfortunately there are some algorithms (e.g iterating a list) where the loading instruction is not easily found. Also this optimization is highly system-dependent. 
  487.     Any persistent object may have references to other objects. If now an object is loaded into memory it is checked wether one or several of its references  is an OID of an already loaded object. In that case the OID is replaced with the actual pointer value. Thereby further traps, when dereferencing this pointer, are prevented. 
  488.     The last optimiziation strategy keeps a list (cache) of the lastest n loaded objects. When a trap appears, the requested persistent object is loaded and an iteration over this list shows whether these objects have reference the newly loaded object. If this is the case, the references are set to the correct memory address. The two last optimiziation strategies are especially suited for iterations over lists and other similiar algorithms.
  489. These three optimiziation strategies prevent a considerable number of traps. Most test cases showed almost optimal behaviour. 
  490.     Another problem is the correct setting of global references, to prevent loaded persistent data to be freed by the garbage collector. To prevent this, we introduce a new phase in the garbage collector: the prepare phase (see also section 
  491. ). The gargbage collector offers a list of procedures Kernel.prepQ which are handled before the mark phase is started. Any user can register his own procedures. Oberon-D registers the procedure PersSys.SetGlobPersVars, which scans all global pointers to find references containing the negative OID of an already loaded object. These references are set to the correct memory addresses.
  492. 3.4 Persistent Memory Management
  493. The persistent memory management consists of the root mechanism, the list of persistent types, the necessary processes to make an object persistent, and the persistent garbage collector.
  494. 3.4.1 Persistent Roots
  495. Oberon-D makes everything persistent what is reachabe through a persistent root. For this purpose a list of the persistent roots is maintained by Oberon-D. This is done by the module PersKernel with the help of a simple list, sorted by the root-key. The list can be iterated through the procedure IterateRoots. When initializing the database, this list is loaded from an simple Oberon file, which is updated on every change to the list. 
  496. 3.4.2 Persistent Types
  497. Oberon-D needs special information about the type of a persistent object. This information is stored in the module PersTypes, which maintaines a list of persistent types. The type information contains the type name, the inclosing module, a unique number (for a shorter type identification), the registered read and write mappers (if existing), the size, and the pointer offsets (on the disk). If mappers are registered, the size and pointer offsets can differ between the transient and persistent representation. The pointer offset information is needed for the  persistent garbage collection (see section 
  498.     If an object of a type, not contained in the list of persistent types, is made persistent, the type is added to this list. Default values, e.g. automatic mappers, will be taken as properties. Adding to the list with non standard values, is possible through the function Persistent.RegisterType, which calls the function PersTypes.Register (see section ???).
  499. 3.4.3 Making a transient object persistent
  500. If a transient object is made persistent (through a call of Persistent.WriteObj or Persistent.SetRoot) several things are done:
  501.     Determining the persistent type pt (see section 
  502. ) of the object.
  503.     Allocating space on the persistent heap.
  504.     Register the object in the finaliziation queue (see sections 
  505.  and 
  506.     Register the object's OID and its memory address in the two relations (memory addresse->OID) and (OID->memory addresse).
  507. 3.4.4 Persistent Garbage Collection
  508. Oberon-D uses the garbage collection algorithm Stop & Copy [????] to delete obsolete persistent data. This algorithm (implemented in PersKernel.GC) uses two heaps (files) and copies all accessible object from the heap fromHeap to the heap toHeap. The algorithm consists of three steps:
  509.     Copy all root objects (by iterating the root list with IterateRoots) (see fig. ????).
  510.     Move Scan-Pointer through objects: Copying of all accessible object from the fromHeap to the toHeap until scan = free (see fig. ???).
  511.     Problem: pointers from toHeap to already copied objects in fromHeap (e.g. cyclic structures).
  512.     after copying: mark old object in fromHeap by overwriting it with pointer of copied object.
  513.     pointer from the fromHeap to the toHeap: bend to copied object (see fig. ???.).
  514.     Swap fromHeap and toHeap.
  515. This algorithm can be optimized by caching fromHeap and toHeap in the transient memory. Nearly the complete memory can be used for this purpose.
  516. 3.5 Programming Interfaces
  517. 3.5.1 PersKernel
  518. Module PersKernel is responsible for the persistent memory management and the list of persistent roots. It allocates memory on the persistent heap, sweeps garbage from the persistent heap, and allows to set and remove persistent roots.
  519. DEFINITION PersKernel;
  520.     IMPORT Files;
  521.     CONST
  522.         ok = 0; alreadyExists = 1; undefinedRoot = 2; databaseOnline = 3; (* error codes *)
  523.         array = "a"; record = "r";  (* start signs *)
  524.     TYPE
  525.         Root = POINTER TO RootDesc;
  526.         RootDesc = RECORD
  527.             oid-: LONGINT;
  528.             key-: RootName;
  529.         END ;
  530.         Notifier = PROCEDURE (VAR r: Root);   
  531.         RootName = ARRAY 32 OF CHAR;
  532.         heap: Files.File;
  533.         heapSize-, number-: LONGINT;
  534.         res: INTEGER;
  535.     PROCEDURE GC;
  536.     PROCEDURE IterateRoots (proc: Notifier);
  537.     PROCEDURE NewSys (VAR oID: LONGINT; size: LONGINT);
  538.     PROCEDURE OID (str: ARRAY OF CHAR): LONGINT;
  539.     PROCEDURE RegisterRoot (str: ARRAY OF CHAR; oid: LONGINT);
  540.     PROCEDURE RemoveRoot (str: ARRAY OF CHAR);
  541. END PersKernel.
  542. State
  543.     heap is the file containing the persistent heap.
  544.     heapSize is the current size of the persistent heap.
  545.     number is the current number of persistent objects.
  546.     res indicates the success of an operation.
  547. Operations
  548.     GC starts the persistent Garbage Collector (see also OberonD.GC).
  549.     NewSys (oid, s) allocates persistent memory for an object of size s. The object's OID is retrieved in oid.
  550.     OID (str) returns the object identifier of the persistent root object given by the key str.
  551.     RegisterRoot (str, oid) registers the persistent object with object identifier oid as a persistent root with key str. The list of roots is automatically stored after a new root has been registered.
  552.     RemoveRoot (str) removes the persistent root with key str. Afterwards, the object referenced by the root str still exists, but only as a transient and not anymore as a persistent object. The list of roots is automatically stored after a root has been removed.
  553.     IterateRoots (proc) iterates the list of persistent roots and calls the procedure variable proc for each found root.
  554. 3.5.2 PersTypes
  555. Module PersTypes manages the persistent types. This module corresponds to the module Types, which manages the transient types. PersTypes is necessary for the storing of the unique type number, the read and write mappers, and the pointer offsets on the persistent heap.
  556. DEFINITION PersTypes;
  557.     IMPORT SYSTEM, Types, Modules;
  558.     CONST
  559.         ok = 0; mapperNotRegistered = 4;  (* error codes *)
  560.     TYPE
  561.         Mapper = PROCEDURE (o: SYSTEM.PTR);
  562.         PtrArr = POINTER TO ARRAY OF LONGINT;
  563.         Type = POINTER TO TypeDesc;
  564.         TypeDesc = RECORD
  565.             nr-: INTEGER;   (* unique type number *)
  566.             name-, module-: ARRAY 32 OF CHAR; (* name of type, defining module *)
  567.             auto-: BOOLEAN; (* automatic or registered mappers *)
  568.             rmap-, wmap-: Mapper; (* mappers  *)
  569.             size-: LONGINT; (* size of an object on the persistent heap *)
  570.             nofptrs-: INTEGER; (* number of pointers on the persistent heap *)
  571.             ptrArr-: PtrArr; (* locations of the pointers *)
  572.         END ;
  573.         res: INTEGER;
  574.     PROCEDURE Register (t: Types.Type; read, write: Mapper; size: LONGINT; VAR ptrArr: ARRAY OF LONGINT);
  575.     PROCEDURE This (mod, name: ARRAY OF CHAR): Type;
  576.     PROCEDURE ThisByNr (nr: INTEGER): Type;
  577. END PersTypes.
  578. Operations
  579.     ThisByNr (nr) returns the persistent type with the unique number nr.
  580.     This (mod, name) returns the persistent type corresponding to the transient type name defined in module mod.
  581.     Register (t, read, write, s, ptrArr) registers a persistent type identified by t with the mappers read and write. An object of this type will need s bytes on the persistent heap and its pointer offsets are given by ptrArr. PersTypes generates a unique number as the access key for this type. This procedure is called by Persistent.RegisterType.
  582. 3.5.3 PersSys
  583. PersSys contains all system dependent operations of Oberon-D. Porting Oberon-D from one system to another (e.g. from PowerMac-Oberon to Windows-Oberon) means to port this module. The following operations are only for internal use.
  584. Browser.ShowDef PersSys  ????
  585. Operations for scanning the reference information
  586.     FindProc (adr, mod, proc) returns the procedure information (mod, proc) of the procedure starting at the address adr.
  587.     StartPC (mod, proc) : LONGINT returns the starting address of the procedure defined by (mod, proc).
  588. Trap Handling
  589.     NilTrap (ctx) returns TRUE, if the trap described by the context ctx was produced by a nil-Trap [???].
  590.     UnmappedMemoryTrap (ctx) returns TRUE, if the trap desribed by ctx was produced by an unmapped memory Trap [????].
  591. Type Information
  592.     AnonymType (m, bt, ptr, nofptrs, s) returns an anonymous type, defined in module m, derived from type bt, with nofptrs pointers, described by ptr, and with object size s.
  593.     GetPtr (t, ptrArr, nofptrs) retrieves the number of pointers nofptrs from type t, and their offsets in ptrArr.
  594.     NewObj (o, t) retrieves a new object o of type t. The contrast to Types.NewObj, no type test on the static type of o is done.
  595.     Size (t) returns the necessary size (on the transient heap) for an object of type t.
  596. Array Information
  597.     CheckIfArray (o, cond, t) checks if o is a dynamic array and retrieves the result in cond. Furthermore the type of o, if it is not an array, or , if it is an array, the type of its elements, is retrieved in t.
  598.     GetArrInf (o, first, last, nofelem, s) retrieves following array information about the array o: The number of array elements nofelem, the memory borders first and last, and the necessary size on the transient heap s.
  599.     NewArr (t, dim, nofdim, new, first, last) allocates a new array at address new. Its dimensions are described by nofdim and dim. The array elements are of type t. The memory borders first and last are retrieved.
  600.     ReadSimpleArr (r, o) reads a simple dynamic array (e.g. ARRAY TO POINTER OF CHAR) o, meaning array information and contents, from rider r.
  601.     StoreArrInf (r, o, first, last, nofelem) stores the array information of array o on the rider r. The number of array elements nofelem, and the memory borders first and last are retrieved.
  602. Optimization
  603.     SetGlobPersVars scans all global pointers to find references containing an OID of an already loaded object and sets them to the correct memory addresses.
  604. Miscellaneous
  605.     SetStaticBase (proc, m) sets the static base of the procedure variable proc to the right value, determined by the properties of module m. This is necessary with eight bytes sized procedure variables, e.g. in PowerMac-Oberon.
  606.     MapAutomatic (r, o, t, read, mark) maps the object o, which is of type t, to the rider r. read determines whether the object should be read or written. mark determines whether the system is in the mark-phase or not. If read is FALSE and mark TRUE, the object is not really written, but all referenced objects are marked as persistent.
  607. 3.6 Necessary changes in the Oberon System
  608. 3.6.1 Compiler 
  609.     The loading of persistent objects into the transient heap is managed by the trap handler (see 
  610. ). Therefore, each access to a property of a not yet loaded persistent object must cause a trap. Because of the existing NIL-Check (twi in the compiler implementation for the PowerMac) before such accesses, we decided to use this trap. We changed the NIL-Trap such that it causes a trap when a pointer contains a value equal or lower zero. Lower zero means, that the object is persistent and contains a negative OID (see 
  611. ). This is just a small change to one line of the Oberon compiler [Cre90].
  612. 3.6.2 Kernel
  613.     Object finalization has been made safe. Any Oberon application may now register objects for finalization, together with a finalization procedure. When the garbage collector reclaims memory, it first calls the registered finalization procedure, which may perform cleanup operations. 
  614.     The module Kernel exports a new procedure RegisterObject (obj: SYSTEM.PTR; fin: Finalizer) and a new type Finalizer = PROCEDURE (obj: SYSTEM.PTR).
  615.     The object finalization implementation is based on [Tem94, p. 83ff].
  616.     Procedure queues have been added. A procedure queue is a list of procedures which are handled at a well defined moments. There are four different queues, handled at different times: prepQ (handled at the beginning of the garbage collector), gcQ (between mark and sweep phase), closeQ (after the garbage collector) and quitQ (before leaving the Oberon system).
  617.     The definition of this queues can be seen below:
  618.         Notifier = PROCEDURE;
  619.         Queue = RECORD
  620.             PROCEDURE (VAR q: Queue) Add (notify: Notifier);
  621.             PROCEDURE (VAR q: Queue) Handle;
  622.             PROCEDURE (VAR q: Queue) Init;
  623.             PROCEDURE (VAR q: Queue) Remove (notify: Notifier);
  624.         END ;
  625.     The transient garbage collector (Kernel.GC) had to be adapted, as global variables and heap addresses can now contain values lower than zero.
  626. 4. Conclusions
  627. Oberon-D is an ongoing project. Persistence was but the first step. There are other goals, like platform-independence, simplicity and extensibility. With the exception of some mentioned non-portable parts (see sections 
  628.  and 
  629. ), these goals have been reached. The next steps will be the inclusion of other database functionalities in the Oberon system:
  630.     Schema Evolution: Many object oriented database systems allow the user to modify type definitions. However, they vary considerably in the amount of assistance they offer in handling such modifications. An example: When adding a new attribute to an object type, is it necessary to explicitly "fix" all existing objects of the changed type to include the new attribute? Is it possible to add a new supertype when instances of a type exist? [Cat94, p.116ff].
  631.     Recovery: Recovery mechanisms allow a consistent state of the database systems to be recovered after a system crash.
  632.     Query Languages: Query languages are important functionalities of database systems. The user can retrieve data by specifying the conditions the data must meet. In relational database systems, query languages are the only way to access data, wheras object-oriented database systems, in general, have two ways. The first, in Oberon-D already implemented, is navigational and exploits object identifiers and aggregation hierarchies. Given an OID, the system accesses the object directly and it navigates through the objects referred to by its attributes. The second is access through a query language.
  633.     Concurrency control limits simultaneous reads and updates by different users, to give all users a consistent view of the data [Cat94, p.69ff]. Although Oberon-D is an one-user database system, there may be different tasks working simultaneously on the persistent heap.
  634. Additionally the inclusion of persistence in the Oberon system, may be improved in the future. Some ongoing directions are:
  635.     Optimiziation of the persistent garbage collection.
  636.     Storage of extended reference information to avoid some restrictions mentioned in section 
  637.     Usage of more than one heap.
  638.     Porting Oberon-D to additional platfoms besides Windows and PowerMac (e.g. Sparc, Linux, ...).
  639. Acknowledgments
  640. I wish to thank Prof. Hanspeter M
  641. ssenb
  642. ck and Prof. Gerti Kappel for their support of this project. My thank goes also to Markus Hof, Christian Mayrhofer, Christoph Steindl and Josef Templ for many stimulating discussions about Oberon-D. Markus Hof - sitting on the other side of my desk - was also responsible for porting Oberon to the PowerMac, and therefore could give me necessary information to implement the module PersSys. Josef Templ's PhD thesis "Metaprogramming in Oberon" was a rich source of inspiration for Oberon-D.
  643. Appendix ????: Persistent Heap File Format
  644. PersistentHeap =        Header {Object}.
  645. Header =                version nofObjects.
  646. Object =                 Record | Array.
  647. Record =                recSign TypeInf Elem.
  648. Array =                    arrSign TypeInf ArrInf.
  649. TypeInf =                nr | SpecialTD | SystemTD | AnonymTD.
  650. SpecialTD =             0 0.
  651. SystemTD =             0 1.
  652. AnonymTD =            size realSize baseTypenr modName Pointers.
  653. Pointers =                {ptroff} -1.
  654. ArrInf =                 ArrPtr | ArrRec | ArrChr.
  655. ArrPtr =                    Dims oid {oid}.
  656. ArrRec =                    Dims Elem
  657. Dims =                    dim {dim} 0
  658. ArrChr =                byte {byte}.
  659. Elem =                     mapper {mapper}.
  660. Remarks:
  661.     version, nofObjects, size, realSize, baseTypenr, ptroff, dim, oid are identifiers of Oberon type LONGINT.
  662.     byte is an identifier of Oberon type SYSTEM.BYTE.
  663.     modName and mapper are identifiers of type ARRAY 32 OF CHAR.
  664.     recSign and arrSign are exported constants of module PersKernel (PersKernel.record and PersKernel.array).
  665. Appendix 
  666. ???: ISFiles
  667. Oberon-D uses several B-Trees [BaMc72], e.g. for the relation (memory address->OID). These trees are implemented in the module ISFiles.
  668.     This module provides a set of routines for 'index sequential files'. An index sequential file consists of (key, data) pairs. With help of the key, one gets the corresponding data. The length of the key and data is arbitrary and is defined when a new file is created. 
  669. DEFINITION ISFiles;
  670.     IMPORT SYSTEM;
  671.     CONST
  672.         ok = 0; errAccessMode = 1; errIllegalKey = 2; errDiskFull = 3; 
  673.         errRecNotFound = 4;  (* error codes *)
  674.         readOnly = 1; readWrite = 2; (* access modes *)
  675.     TYPE
  676.         File = POINTER TO FileDesc;
  677.         FileDesc = RECORD
  678.             status-: INTEGER; (* error field *)
  679.             totEntries-: LONGINT; (* number of entries *)
  680.             ks-, ds-: INTEGER; (* keysize, datasize *)
  681.         END ;
  682.     PROCEDURE Close (f: File);
  683.     PROCEDURE Create (name: ARRAY OF CHAR; keySize, dataSize: INTEGER): File;
  684.     PROCEDURE Delete (f: File; VAR key: ARRAY OF SYSTEM.BYTE);
  685.     PROCEDURE Open (name: ARRAY OF CHAR; mode: INTEGER): File;
  686.     PROCEDURE Read (f: File; VAR key, data: ARRAY OF SYSTEM.BYTE);
  687.     PROCEDURE ReadNext (f: File; VAR key, data: ARRAY OF SYSTEM.BYTE);
  688.     PROCEDURE Restart (f: File);
  689.     PROCEDURE Write (f: File; VAR key, data: ByteArrDesc);
  690. END ISFiles.
  691. Operations
  692.     Create (name, keySize, dataSize) creates a new index sequential file with the name name. The file is opened for read and write operations. 
  693.     Open (name, mode) opens an existing index sequential file name. The parameter mode specifies the access mode. There are two different modes. readOnly, if only read operations, or readWrite, if read and write operations are intended.
  694.     Close (f) closes the file f opened previously with Create or Open.
  695.     Read (f, key, data) reads the data associated with key and retrieves it in data. If an error occured, the error code is stored in f.status. Otherwise, f.status is ok. Possible errors are errAccessMode, if the file was previously closed or errIllegalKey, if there exists no data with the given key. 
  696.     ReadNext (f, key, data) reads the data and key from the file f with the smallest key bigger than the key of the most recently read data. If an error occured, the error code is stored in f.status. Otherwise, f.status is ok. Possible errors are errAccessMode, if the file was previously closed or errRecNotFound, if no further data with a bigger key exists.
  697.     Restart (f) resets the file f back to its start. A following ReadNext operation returns the first (key, data) pair in the file (data with smallest key).
  698.     Write (f, key, data) overwrites the data - associated with the key key  - with data. If no pair with this key exists, a new one is generated. If an error occures, the error code is stored in f.status. Otherwise, f.status is ok. Possible errors are errAccessMode, if the file is not open for write access or errDiskFull, if the block couldn't be allocated.
  699.     Delete (f, key) deletes the pair with the key key from the file f. If an error occures, the error code is stored in f.status. Otherwise f.status is ok. Possible errors are errAccessMode, if file is not open for write access or errIllegalKey, if no block with this key exists.
  700. Errors
  701. All operations may not succeed with there task. If a problem occurs, an error is returned. With Open and Create an error is indicated by a return value of NIL. All other functions use the File property status to indicate their success or failure.
  702. Appendix 
  703. ???: Error-Codes
  704. 0: Ok, no error.
  705. 1: This root already exists.
  706. 2: There is no root with a corresponding name.
  707. 3: The database must be offline (before starting persistent garbage collection).
  708. 4: The mappers were not registered.
  709. Appendix ??: How to get Oberon-D
  710. Oberon-D is part of the Oberon version for the PowerMac. This distribution can be obtained via anonymous internet file transfer ftp (at no charge).
  711.         FTP Hostname:             Oberon.ssw.uni-linz.ac.at
  712.         FTP Directory:                /pub/Oberon/PowerMac
  713. Appendix ???: References
  714. [AgGe89]    R.Agrawal and N.H.Gehani,
  715.             "Ode (Object Database and Environment): 
  716.                 The Language and the Data Model"
  717.             Proc. ACM_SIGMOD 1989 Int'l Conf. Management of Data, Portland,
  718.             Oregon, May - June 1989, pp. 36-45
  719. [AtBu87]    M.P.Atkinson, O.P.Buneman,
  720.             "Types and Persistence in Database Programming Languages"
  721.             ACM Computing Surveys, Vol. 19, No.2, June 1987, pp. 105-190
  722. [BaMc72]    R.Bayer, E.M.McCreight
  723.             "Organization and Maintenance of Large Ordered Indexes"
  724.             Acta Informatica, 1, No. 3 (1972), pp. 173-189
  725. [Cat94]        R.G.G.Cattel, "Object Data Management"
  726.             Addision-Wesley, 1994
  727. [Cre90]        R.Crelier, "OP2: A Portable Oberon Compiler"
  728.             Computersysteme ETH Z
  729. rich, Report No. 125, February 1990
  730. Wi91]    H.M
  731. ssenb
  732. ck, N.Wirth: "The Programming Language Oberon-2"
  733.             Structured Programming Vol. 12, No. 4, 1991, pp. 179 - 195
  734.                         
  735. [Rei91]        M.Reiser,
  736.             "The Oberon System - User Guide and Programmer's Reference"
  737.             Addision-Wesley, 1991
  738. [ReWi92]    M. Reiser, N.Wirth, "Programming in Oberon"
  739.             Addison-Wesley, 1992
  740.                         
  741. [Tem94]    J.Templ, "Metaprogramming in Oberon"
  742.             PhD Dissertation. ETH Z
  743. rich 1995
  744. [WiGu89]    N.Wirth, J.Gutknecht, "The Oberon System"
  745.             Software - Practice and Experiences, 19, 9 (Sept. 1989)    
  746.